#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
局面评估
author: yooongchun@foxmail.com
"""
import numpy as np
from typing import List, Tuple
from itertools import combinations as comb
from collections import defaultdict as ddict

import chessboard
from util import Point, Chess, Score, Direction, cases


class Evaluation(object):
    """棋盘评估类
    根据最后的points步棋, 分别对黑棋和白棋进行打分
    """
    def __init__(self, board: chessboard.ChessBoard, points: List[Point]):
        """初始化
        Args:
            board: 棋盘
            points: 指定评分的点序列
        """
        # 这三个变量关联了序列的表示符号，不可更改
        self.x = Chess.BLACK
        self.d = Chess.WHITE
        self.o = Chess.EMPTY

        self._size = board.size + 2
        self._matrix = np.array(board.get_board(with_boder=Chess.WHITE), dtype=np.int8)
        self._points = np.array(points, dtype=np.int8) + 1

        # 位置分，越靠近中心，得分越高
        self._pscore = self.position_score(Chess.BLACK)

    def position_score(self, who: int):
        """位置分,越靠近中心越高"""
        pscore = 0
        for pt in self._points:
            if who == self._matrix[pt.row, pt.col]:
                pscore += min([pt[0], pt[1], self._size - pt[0], self._size - pt[1]])
        return pscore

    def _gen_array(self, min_line=4):
        """根据点序列生成对应的行、列、对角线线段；注意需要去重处理"""
        # 保存首尾两个点
        hori_arr = set()
        vert_arr = set()
        diag_arr = set()
        rdiag_arr = set()
        for r, c in self._points:
            # 水平线
            hori_arr.add((Point(pt.row, 0), Point(pt.row, self._size-1)))
            # 竖直线
            vert_arr.add((Point(0, pt.col), Point(self._size-1, pt.col)))
            # 逆对角线
            step_right = min(pt.row, self._size-1-pt.col)
            step_left = min(pt.col, self._size-1-pt.row)
            start_row, end_row = pt.row - step_right, pt.row + step_left
            start_col, end_col = pt.col + step_right, pt.col - step_left
            # 满足序列最短长度才会加入（需额外刨除加入的边框2层
            if end_row - start_row - 1 >= min_line:
                rdiag_arr.add((Point(start_row, start_col), Point(end_row, end_col)))
            # 斜对角线
            step_start = min(pt.row, pt.col)
            step_end = min(self._size-1-pt.col, self._size-1-pt.row)
            start_row, end_row = pt.row - step_start, pt.row + step_end
            start_col, end_col = pt.col - step_start, pt.col + step_end
            if end_row - start_row - 1 >= min_line:
                diag_arr.add((Point(start_row, start_col), Point(end_row, end_col)))
        return hori_arr, vert_arr, diag_arr, rdiag_arr

    def _trans(self, val: int):
        """转换表示"""
        if val == Chess.EMPTY:
            return "o"
        if val == Chess.BLACK:
            return "x"
        return "d"

    def _point2lines(self, min_line:int=4):
        """首先用指定点构建出对应的行、列、对角线上的线段，注意这些线段需要去重处理
        """
        # 只需要查找指定类型的点，且同一行同一列只需要一个点
        hori_arr, vert_arr, diag_arr, rdiag_arr = self._gen_array(min_line=min_line)
        # 生成四个方向的四条线
        lines = {Direction.HORI: [], Direction.VERT: [], Direction.DIAG: [], Direction.RDIAG: []}
        chars = {Direction.HORI: [], Direction.VERT: [], Direction.DIAG: [], Direction.RDIAG: []}
        for pt, _ in hori_arr:
            for c in range(self._size):
                lines[Direction.HORI].append(Point(pt.row, c))
                chars[Direction.HORI].append(self._matrix[pt.row][c])
        for pt, _ in vert_arr:
            for r in range(self._size):
                lines[Direction.VERT].append(Point(r, pt.col))
                chars[Direction.VERT].append(self._matrix[r][pt.col])
        for pt1, pt2 in diag_arr:
            rows = range(pt1.row, pt2.row+1)
            step = int(pt1.col < pt2.col) * 2 - 1 # 正向序列为1反向为-1
            cols = range(pt1.col, pt2.col + step, step)
            for r, c in zip(rows, cols):
                lines[Direction.DIAG].append(Point(r, c))
                chars[Direction.DIAG].append(self._matrix[r][c])
        for pt1, pt2 in rdiag_arr:
            rows = range(pt1.row, pt2.row+1)
            step = int(pt1.col < pt2.col) * 2 - 1 # 正向序列为1反向为-1
            cols = range(pt1.col, pt2.col + step, step)
            for r, c in zip(rows, cols):
                lines[Direction.RDIAG].append(Point(r, c))
                chars[Direction.RDIAG].append(self._matrix[r][c])
        chars_arr = {}
        for dire in chars:
            chars_arr[dire] = ''.join(self._trans(v) for v in chars[dire])
        return lines, chars_arr

    def _search_case_arr(self, chars: dict, lines: dict, multi_arr: dict, case: Tuple[str, float]):
        """计算得分"""
        score = 0
        case_arr, case_score = case
        for dire in chars:
            idx = chars[dire].find(case_arr)
            if idx != -1: # 在该方向找到了case
                score += case_score
                # 记录下对应case的方向所在直线和分数
                if case_score >= Score.DIED3: # 仅加入Died3及以上的case
                    multi_arr[dire].append((lines[dire][idx: idx+len(case_arr)], case_score))
        return score

    def black_score(self):
        """最大分"""
        # 根据查找点生成对应四个方向的查找线
        lines, chars = self._point2lines(4)
        # 总分是每种case分数的累加
        total_score = 0
        # 记录不同方向的序列
        multi_arr = ddict(list)
        # 计算单序列总分
        for case in cases:
            # 计算case的得分
            case_score = self._search_case_arr(chars, lines, multi_arr, case)
            total_score += case_score
            # 判断是否对称，如不对称则需要搜索反转情况
            rcase_arr = case[0][::-1]
            if rcase_arr != case[0]:
                case_score = self._search_case_arr(chars, lines, multi_arr, (rcase_arr, case[1]))
                total_score += case_score
        # 计算双序列得分是对应单序列的乘积
        if len(multi_arr) >= 2: # 至少匹配两种类型线才考虑双序列相交的情况
            for pt in self._points: # 对每一个点判断是否存在至少两条线经过他
                for dire1, dire2 in comb(multi_arr, 2): # 不同方向两两组合判断是否相交
                    for line1, score1 in multi_arr[dire1]:
                        for line2, score2 in multi_arr[dire2]:
                            if pt in line1 and pt in line2: # 点在两条直线上
                                total_score += score1 * score2
        return self._pscore + total_score

    def _convert(self):
        """翻转board"""
        for i in range(1, self._size-1):
            for j in range(1, self._size-1):
                self._matrix[i][j] *= -1

    def white_score(self):
        """白色子得分"""
        self._convert()
        score = self.black_score()
        self._convert()
        return score
